﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Specialized;
using System.Collections;
using System.Web.UI.WebControls;

namespace IdeaNMR.Core.Web
{
    public class SetupFassade : INMRObjectFassade, ICloneable, IEquatable<SetupFassade>
    {
        /// <summary>
        /// Id of corresponding entry in the database.
        /// If not set, means it is not saved to the db.
        /// </summary>
        public Guid Id { get; set; }
        public string Name { get; set; }
        public byte Dimentionality { get; set; }
        public string Info { get; set; }
        public string Type { get; set; }
        public string Script { get; set; }
        public DateTime Datetime { get; set; }

        public bool IsPrivate { get; set; }
        public bool IsDraft { get; set; }

        public int VersionNumber { get; private set; }
        public string VersionInfo { get; set; }
        public Guid FirstVersionId { get; set; }
        public Guid TemplateId { get; set; }

        private List<Author> _authors;
        public List<Author> Authors
        {
            get
            {
                this._authors = this._authors.OrderBy(a => a.Index).ToList<Author>();
                return this._authors;
            }
        }

        private List<ComponentItem> _components;
        public List<ComponentItem> Components
        { get { return this._components; } }

        private List<DevelopmentVersion> _developments;
        public List<DevelopmentVersion> Developments
        { get { return this._developments.OrderByDescending(d => d.DateTime).ToList<DevelopmentVersion>(); } }


        /// <summary>
        /// Shallow copy of only user defined fields.
        /// </summary>
        /// <param name="sf"></param>
        public void CopyTo(SetupFassade sf)
        {
            sf.Name = this.Name;
            sf.Dimentionality = this.Dimentionality;
            sf.Info = this.Info;
            sf.Type = this.Type;
            sf.Script = this.Script;

            sf._authors.Clear();
            sf._authors.AddRange(this._authors);
            sf._components.Clear();
            sf._components.AddRange(this._components);
        }

        #region ICloneable Members

        public object Clone()
        {
            SetupFassade clone = new SetupFassade();
            clone.Id = this.Id;
            clone.Name = this.Name;
            clone.Dimentionality = this.Dimentionality;
            clone.Info = this.Info;
            clone.Type = this.Type;
            clone.Script = this.Script;
            clone.Datetime = this.Datetime;
            clone.IsPrivate = this.IsPrivate;
            clone.IsDraft = this.IsDraft;
            clone.VersionNumber = this.VersionNumber;
            clone.FirstVersionId = this.FirstVersionId;
            clone.TemplateId = this.TemplateId;
            clone._authors.AddRange(this._authors);
            clone._components.AddRange(this._components);
            clone._developments.AddRange(this._developments);

            return clone;
        }

        #endregion

        #region IEquatable<SetupFassade> Members
        public override int GetHashCode()
        {
            int result = this.Name.GetHashCode();
            result ^= this.Dimentionality.GetHashCode();
            result ^= this.Info.GetHashCode();
            result ^= this.Type.GetHashCode();
            result ^= this.Script.GetHashCode();
            //result ^= this.Datetime.GetHashCode();
            //result ^= this.IsPrivate.GetHashCode();
            //result ^= this.IsDraft.GetHashCode();
            //result ^= this.VersionNumber.GetHashCode();
            //result ^= this.FirstVersionId.GetHashCode();

            foreach (Author a in this._authors)
                result ^= a.GetHashCode();
            foreach (ComponentItem cI in this._components)
                result ^= cI.GetHashCode();

            return result;
        }

        public override bool Equals(object obj)
        {
            if (!(obj is SetupFassade))
                throw new InvalidCastException("The 'obj' argument is not a SetupFassade object.");
            else
                return Equals(obj as SetupFassade);
        }

        public bool Equals(SetupFassade other)
        {
            if (other == null) return false;
            int hcTHis = this.GetHashCode();
            int hcOther = other.GetHashCode();

            return this.GetHashCode() == other.GetHashCode();
        }
        #endregion

        #region INMRObjectFassade Members
        public ComponentType ComponentType
        {
            get { return ComponentType.Setup; }
        }

        /// <summary>
        ///     Saves the setup to the database.
        /// </summary>
        /// <param name="userName">
        ///     Used to fetch the PRIVATE development versions for this setup after it is saved.
        /// </param>
        /// <remarks>
        ///     <para>
        ///         <paramref name="userName"/> is used because there might be development versions created by different users.
        ///         Only those developed by current user must be selected.
        ///     </para>
        /// </remarks>
        public void SaveToDatabase(string userName)
        {
            IdeaNMRdbContainer inmr = new IdeaNMRdbContainer();

            Setup entry = null;
            // Get the entry if it is already exists in db
            if (this.Id != Guid.Empty)
            {
                entry = (from en in inmr.BaseObjects.OfType<Setup>()
                         where en.Id == this.Id
                         select en).FirstOrDefault();
                if (entry != null)
                {
                    if (!entry.Authors.IsLoaded) entry.Authors.Load();
                    NumberedAuthor[] nas = entry.Authors.ToArray<NumberedAuthor>();
                    foreach (NumberedAuthor na in nas) inmr.DeleteObject(na);

                    if (!entry.NamedComponents.IsLoaded) entry.NamedComponents.Load();
                    NamedComponent[] ncs = entry.NamedComponents.ToArray<NamedComponent>();
                    foreach (NamedComponent nc in ncs) inmr.DeleteObject(nc);
                }
            }

            if (entry == null) // Create new database entry
            {
                entry = new Setup();
                entry.Id = this.Id;

                Setup fV = null;
                if (this.FirstVersionId != Guid.Empty)
                    fV = (from en in inmr.BaseObjects.OfType<Setup>()
                          where en.Id == this.FirstVersionId
                          select en).FirstOrDefault();
                else fV = entry;
                if (fV == null) throw new InvalidOperationException("There is not FirstVersion with given Id.");
                entry.FirstVersion = fV;

                Setup tE = null;
                if (this.TemplateId != Guid.Empty)
                    tE = (from temp in inmr.BaseObjects.OfType<Setup>()
                          where temp.Id == this.TemplateId
                          select temp).FirstOrDefault();
                else tE = entry;
                if (tE == null) throw new InvalidOperationException("There is not Template with given Id.");
                entry.Template = tE;
            }

            entry.Datetime = this.Datetime;
            entry.Name = this.Name;
            entry.Dimentionality = this.Dimentionality;
            entry.Info = this.Info;
            entry.Type = this.Type;
            entry.Script = this.Script;
            entry.IsPrivate = this.IsPrivate;
            entry.IsDraft = this.IsDraft;

            entry.VersionNumber = this.VersionNumber;
            entry.VersionInfo = this.VersionInfo;

            if (this.FirstVersionId == this.Id)
            {
                entry.FirstVersion = entry;
                entry.Template = entry;
            }


            // Authors
            foreach (Author a in this._authors)
            {
                NumberedAuthor na = new NumberedAuthor();
                na.Id = Guid.NewGuid();
                na.Index = a.Index;
                na.User = (from u in inmr.Users
                           where u.Username == a.Username
                           select u).FirstOrDefault();

                entry.Authors.Add(na);
            }

            // Components
            foreach (ComponentItem cI in this._components)
            {
                NamedComponent nC = new NamedComponent();
                nC.Id = Guid.NewGuid();
                nC.Identifier = cI.Identifier;
                nC.Component = (from c in inmr.BaseObjects.OfType<Component>()
                                where c.Id == cI.Id
                                select c).FirstOrDefault();

                entry.NamedComponents.Add(nC);
            }
            inmr.SaveChanges();

            GetDevelopments(userName, entry);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <param name="userName"></param>
        /// <exception cref="System.UnauthorizedAccessException">Thrown when userName is not found among authors of entry with id.</exception>
        /// <exception cref="System.ArgumentException">Thrown when there is not db entry with id.</exception>
        public void Initialize(Guid id, string userName)
        {
            if (id == Guid.Empty) throw new InvalidOperationException("Cannot initialize draft with Empty Guid.");

            Setup initial;
            IdeaNMRdbContainer inmr = new IdeaNMRdbContainer();

            #region Get Data
            var query = (from item in inmr.BaseObjects.OfType<Setup>()
                         where item.Id == id
                         select item).First<Setup>();

            if (query == null)
                throw new ArgumentException("Cannot find an entry with ID = " + id, "TargetID");
            #endregion
            initial = query;

            this.Id = id;
            this.Name = initial.Name;
            this.Script = initial.Script;
            this.Type = initial.Type;
            this.Info = initial.Info;
            this.VersionNumber = initial.VersionNumber;
            this.Dimentionality = initial.Dimentionality;
            this.Datetime = initial.Datetime;

            if (!initial.FirstVersionReference.IsLoaded) initial.FirstVersionReference.Load();
            this.FirstVersionId = initial.FirstVersion.Id;

            if (!initial.TemplateReference.IsLoaded) initial.TemplateReference.Load();
            this.TemplateId = initial.Template.Id;

            this.IsPrivate = initial.IsPrivate;
            this.IsDraft = initial.IsDraft;

            // Reuse draft
            if (initial.IsDraft) this.Id = initial.Id;

            #region Authors
            this._authors.Clear();
            User u;
            Author a;
            if (!initial.Authors.IsLoaded)
            {
                initial.Authors.Load();
                foreach (NumberedAuthor na in initial.Authors)
                {
                    if (!na.UserReference.IsLoaded) na.UserReference.Load();
                    u = na.User;
                    a = new Author()
                    {
                        Username = u.Username,
                        FirstName = u.Firstname,
                        MiddleName = u.Middlename,
                        LastName = u.Lastname,
                        Index = na.Index
                    };
                    this._authors.Add(a);
                }
            }
            #endregion

            #region Verify Authentication
            if (initial.IsPrivate)
            {
                var authorship = from au in initial.Authors
                                 where au.User.Username == userName || au.User.Username == BuiltinSettings.EngineUsername
                                 select au;
                if (authorship.Count() == 0)
                    throw new System.UnauthorizedAccessException(string.Format("User {0} requested somebody's else private database entry ID='{1}'. Potential SECURITY THREAT.", userName, id));
            }
            #endregion

            #region Components
            // Load components
            this._components.Clear();
            Component c;
            ComponentItem cI;
            ComponentType cT;
            if (!initial.NamedComponents.IsLoaded) initial.NamedComponents.Load();
            foreach (NamedComponent nc in initial.NamedComponents)
            {
                if (!nc.ComponentReference.IsLoaded) nc.ComponentReference.Load();
                c = nc.Component;
                cT = c.GetComponentType();
                cI = new ComponentItem()
                {
                    Id = c.Id,
                    Identifier = nc.Identifier,
                    Content = c.Content,
                    Type = cT,
                    Name = c.Name,
                    Script = c.Script,
                    IsIncludable = (cT == ComponentType.Script) ? ((Script)c).IsIncludable : false
                };
                this._components.Add(cI);
            }
            #endregion

            GetDevelopments(userName, initial);
        }

        private void GetDevelopments(string userName, Setup initial)
        {
            this._developments.Clear();

            if (!initial.TemplateReference.IsLoaded) initial.TemplateReference.Load();

            if (!initial.Template.Developments.IsLoaded)
                initial.Template.Developments.Load();

            DevelopmentVersion ver;
            foreach (NMRObject nobj in initial.Template.DerivedVersions)
            {
                if (!nobj.Authors.IsLoaded) nobj.Authors.Load();
                foreach (var au in nobj.Authors)
                {
                    if (!au.UserReference.IsLoaded) au.UserReference.Load();
                }
                var auth = from au in nobj.Authors
                           where au.User.Username == userName
                           select au;
                if (auth.Count() == 0 && nobj.IsPrivate) continue;
                ver = new DevelopmentVersion()
                {
                    DateTime = nobj.Datetime,
                    Id = nobj.Id,
                    IsPrivate = nobj.IsPrivate,
                    VersionInfo = nobj.VersionInfo
                };
                this._developments.Add(ver);
            }
        }
        #endregion

        public SetupFassade()
        {
            this.Name = string.Empty;
            this.Id = Guid.Empty;
            this.Dimentionality = 0;
            this.Info = string.Empty;
            this.Type = string.Empty;
            this.Script = string.Empty;
            this.Datetime = DateTime.Now;
            this.IsDraft = true;
            this.IsPrivate = true;

            this._authors = new List<Author>();

            this._components = new List<ComponentItem>();
            this._developments = new List<DevelopmentVersion>();
        }
    }
    [Serializable]
    public class Author
    {
        public string Username { get; set; }
        public string FirstName { get; set; }
        public string MiddleName { get; set; }
        public string LastName { get; set; }
        public int Index { get; set; }

        public override int GetHashCode()
        {
            return (this.Username + this.Index.ToString()).GetHashCode();
        }

        public Author()
        { }

        public Author(string userName, int index)
        {
            IdeaNMRdbContainer inmr = new IdeaNMRdbContainer();
            User auth = (from u in inmr.Users
                         where u.Username == userName
                         select u).FirstOrDefault();
            if (auth == null) throw new ArgumentException(string.Format("User {0} not found.", userName));

            this.Username = auth.Username;
            this.FirstName = auth.Firstname;
            this.MiddleName = auth.Middlename;
            this.LastName = auth.Lastname;

            this.Index = index;
        }
    }
    [Serializable]
    public class DevelopmentVersion
    {
        public Guid Id { get; set; }
        public DateTime DateTime { get; set; }
        public string VersionInfo { get; set; }
        public bool IsPrivate { get; set; }
    }
}
